package com.griddroid.computations;

import java.util.Random;
import com.griddroid.exceptions.NonCoherentComputationException;
import com.griddroid.exceptions.NotCalculatedComputationException;

/**
 * This is one of the computations that we implemented in our Grid. Its purpose
 * was to test the Grid (like a benchmark). To calculate an integral in a
 * distributed way, the <a
 * href="http://en.wikipedia.org/wiki/Monte_Carlo_integration">Monte-Carlo
 * Integration Method</a> was used.
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 * 
 */
public class Integral extends Computation
{
	private static final long serialVersionUID = -4468494971760024143L;
	private int base;
	private int end;
	private int loops;
	private String function;

	/**
	 * 
	 * 
	 * @param base
	 *            the lower bound of the integral
	 * @param end
	 *            the upper bound of the integral
	 * @param loops
	 *            the number of random samples that will be generated
	 * @param function
	 *            the mathematical function to integrate. It should have only
	 *            one variable (like 'x')
	 */
	public Integral(int base, int end, int loops, String function)
	{
		this.base = base;
		this.end = end;
		this.function = function;
		this.loops = loops;
	}

	/**
	 * Just a default constructor with no typical use. It sets the following
	 * values: <code>
	 * base = 0;
	 * end = 0;
	 * function = "";
	 * loops = 0;
     * </code>
	 */
	public Integral()
	{
		this.base = 0;
		this.end = 0;
		this.function = "";
		this.loops = 0;
	}

	/**
	 * Executes the calculation of the Integral. It overrides the method
	 * {@link Computation#calculate() calculate()} of the {@link Computation
	 * Computation}. It's an implementation of <a
	 * href="http://en.wikipedia.org/wiki/Monte_Carlo_integration">Monte-Carlo
	 * Integration Method</a>. There's no return value, but instead the
	 * {@link Computation#result result} is set
	 * 
	 * @see Computation
	 */
	@Override
	public void calculate()
	{
		super.calculate();
		Random r = new Random();
		double mean = 0;
		double rand = 0;
		double res = 0;
		for (int i = 1; i <= this.loops; i++)
		{
			rand = r.nextDouble();
			res = evaluateExpression(new String[] { "" + this.getVariable() }, new double[] { this.end * rand }, this.function);
			mean = mean + res * 1.0 / loops;
		}
		super.result = (this.end - this.base + 1) * mean;
	}

	/**
	 * To split an Integral, we just split the loops for which it is evaluated.
	 * So, e.g. if the original integral should be evaluated over 100000 loops,
	 * if we split it in 10 parts, each small Integral will be evaluated on
	 * 10000 loops.
	 * <p>
	 * There is also another idea, to split the range instead, but we have not
	 * tried that yet
	 * 
	 * @param parts
	 *            the number of parts to split the Integral into
	 * @return an array of smaller Integrals, that result from the original one
	 */
	@Override
	public Computation[] split(int parts)
	{
		Integral[] small_integrals;
		int loops_per_part = (this.loops % parts == 0) ? this.loops / parts : (this.loops + this.loops % parts) / parts;

		if (loops_per_part < 1000)
		{
			loops_per_part = 1000;
			parts = (this.loops % loops_per_part == 0) ? this.loops / loops_per_part : this.loops / loops_per_part + 1;
		}

		small_integrals = new Integral[parts];
		for (int i = 0; i < parts; i++)
		{
			small_integrals[i] = new Integral(this.base, this.end, loops_per_part, this.function);
		}
		return small_integrals;
	}

	/**
	 * Merges an array of Integrals into a new one. The final result is the mean
	 * value of all the calculations, or, more formally in statics, the proposed
	 * result
	 * 
	 * @param parts
	 *            array with calculated parts from the same computation
	 * @throws NotCalculatedComputationException
	 *             one or more parts of the array has not been calculated
	 * @throws NonCoherentComputationException
	 *             one or more parts of the array is not an Integral
	 */
	@Override
	public void merge(Computation[] parts) throws NotCalculatedComputationException, NonCoherentComputationException
	{
		double sum = 0D;
		int num = 0;

		for (Computation c : parts)
		{
			if (!(c instanceof Integral))
			{
				throw new NonCoherentComputationException();
			}
			num++;
			sum += c.getResult();
		}

		super.calculated = true;
		super.result = sum / (1.0D * num);
	}

	/**
	 * 
	 * @return a String (latex like) representation of the integral
	 */
	@Override
	public String toString()
	{

		return "\\int_{" + this.base + "}^{" + this.end + "}\\!{" + this.function + "}\\,d" + this.getVariable();
	}

	private char getVariable()
	{
		char var = '\0';
		for (char c : function.toCharArray())
		{
			if (Character.isLetter(c))
			{
				var = c;
			}
		}
		return var;
	}

	/**
	 * 
	 * @return the lower bound of the integral
	 */
	public int getBase()
	{
		return base;
	}

	/**
	 * 
	 * @return the upper bound of the integral
	 */
	public int getEnd()
	{
		return end;
	}

	/**
	 * 
	 * @return the mathematical function to integrate
	 */
	public String getFunction()
	{
		return function;
	}

	/**
	 * 
	 * @return the number of loops to evaluate. The more the loops, the better
	 *         the precision
	 */
	public int getLoops()
	{
		return loops;
	}

	/**
	 * 
	 * @param base
	 *            the lower bound of the integral
	 */
	public void setBase(int base)
	{
		this.base = base;
	}

	/**
	 * 
	 * @param end
	 *            the upper bound of the integral
	 */
	public void setEnd(int end)
	{
		this.end = end;
	}

	/**
	 * 
	 * @param function
	 *            string with a mathematical formula. It should have only one
	 *            variable over which to integrate (multiple integrals not yet
	 *            supported)
	 */
	public void setFunction(String function)
	{
		this.function = function;
	}

	/**
	 * 
	 * @param loops
	 *            the number of times that the integral will be evaluated
	 */
	public void setLoops(int loops)
	{
		this.loops = loops;
	}

	/**
	 * Just a testing function for the Integral alone, to check the
	 * functionality of the class
	 * 
	 * @param args
	 */
	public static void main(String[] args)
	{
		Integral i = new Integral(0, 10, 1000000, "x^2+x");
		Computation[] cparts = i.split(5);
		for (Computation c : cparts)
		{
			c.calculate();
			try
			{
				System.out.println("PART------ " + c.getResult());
			}
			catch (NotCalculatedComputationException e)
			{
			}
		}

		Computation cfinal = new Integral();
		try
		{

			cfinal.merge(cparts);
			System.out.println("ANSWER----- " + cfinal.getResult());

		}
		catch (NotCalculatedComputationException e)
		{
			e.printStackTrace();
		}
		catch (NonCoherentComputationException e)
		{
			e.printStackTrace();
		}

	}
}
